/**
 * <pre>
 * @(#)GridView.java 1.00 2009-8-25
 * 
 * Copyright (c) 1998, 2009 Regular Individual. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * Contributor(s):
 *     Haixu Huang
 * </pre>
 **/
package cn.sh.huang.chart.view.container;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;

import cn.sh.huang.chart.view.AbstractView;
import cn.sh.huang.chart.view.View;

/**
 * <pre>
 * GridView ...
 * 
 * @version 1.0
 * </pre>
 */
public class GridView extends ViewContainer
{
    private final double[] colWidths, rowHeights;
    // 0 <= x < 1 percent of size
    // x >= 1 pixels of size

    private final List list = new ArrayList();
    private final Dimension normalizedSize = new Dimension(0, 0);

    public GridView(int col, int row)
    {
        if (col <= 0) {
            throw new IllegalArgumentException("!ERROR! Negative column count.");
        }
        if (row <= 0) {
            throw new IllegalArgumentException("!ERROR! Negative row count.");
        }

        // Separate the space evenly by default
        colWidths = new double[col];
        double unitPercent = 1D / col;
        for (int i = 0; i < col; i++) {
            colWidths[i] = unitPercent;
        }
        rowHeights = new double[row];
        unitPercent = 1D / row;
        for (int i = 0; i < row; i++) {
            rowHeights[i] = unitPercent;
        }
    }

    public void addView(View view, int col, int row)
    {
        addView(view, col, 1, row, 1);
    }

    public void addView(View view, int col, int colCount, int row, int rowCount)
    {
        if (col < 0 || colCount <= 0 || col + colCount > colWidths.length) {
            throw new IllegalArgumentException("!ERROR! Illegal column span.");
        }
        if (row < 0 || rowCount <= 0 || row + rowCount > rowHeights.length) {
            throw new IllegalArgumentException("!ERROR! Illegal row span.");
        }
        view.setViewContainer(this);
        ViewSpan span = new ViewSpan(view, col, colCount, row, rowCount);
        list.add(span);
        if (normalizedSize.width != 0 && normalizedSize.height != 0) {
            setSpanSize(span);
        }
    }

    public Dimension getMinimumSize()
    {
        return getGridViewSize(false);
    }

    public Dimension getPreferredSize()
    {
        return getGridViewSize(true);
    }

    public void render(Graphics2D g2d)
    {
        for (int i = 0, size = list.size(); i < size; i++) {
            ((ViewSpan) list.get(i)).getView().render(g2d);
        }
    }

    public void setColPercent(int col, double percent)
    {
        if (percent < 1E-4 || percent > 1) {
            throw new IllegalArgumentException("!ERROR! Illegal column percent width.");
        }
        double ratio = (colWidths[col] >= 1) ? 1 - percent : (1 - percent) / (1 - colWidths[col]);
        normalize(colWidths, ratio, col);
        colWidths[col] = percent;
    }

    /**
     * To set the column width, if it's by percent originally, then the percent shared by other columns(which is by
     * percent) evenly
     * 
     * @param col
     * @param width
     */
    public void setColWidth(int col, int width)
    {
        if (width <= 1) {
            throw new IllegalArgumentException("!ERROR! Illegal column width.");
        }
        if (colWidths[col] <= 1) {
            double ratio = 1 / (1 - colWidths[col]);
            normalize(colWidths, ratio, col);
        }
        colWidths[col] = width;
    }

    public void setRowHeight(int row, int height)
    {
        if (height <= 1) {
            throw new IllegalArgumentException("!ERROR! Illegal row height.");
        }
        if (rowHeights[row] <= 1) {
            double ratio = 1 / (1 - rowHeights[row]);
            normalize(rowHeights, ratio, row);
        }
        rowHeights[row] = height;
    }

    public void setRowPercent(int row, double percent)
    {
        if (percent < 1E-4 || percent > 1) {
            throw new IllegalArgumentException("!ERROR! Illegal row percent height.");
        }
        double ratio = (rowHeights[row] >= 1) ? 1 - percent : (1 - percent) / (1 - rowHeights[row]);
        normalize(rowHeights, ratio, row);
        rowHeights[row] = percent;
    }

    public void setViewSize(final int width, final int height)
    {
        normalizedSize.setSize(new Dimension(width, height));
        for (int i = 0; i < colWidths.length; i++) {
            if (colWidths[i] > 1) {
                normalizedSize.width -= colWidths[i];
            }
        }

        for (int i = 0; i < rowHeights.length; i++) {
            if (rowHeights[i] > 1) {
                normalizedSize.height -= rowHeights[i];
            }
        }

        for (int i = 0, size = list.size(); i < size; i++) {
            setSpanSize((ViewSpan) list.get(i));
        }
        super.setViewSize(width, height);
    }

    private int antiNomalize(double[] units, int idx, int count, int len)
    {
        double ratio = 0D;
        for (; count > 0; count--, idx++) {
            if (units[idx] > 1) {
                len -= Math.round(units[idx]);
            } else {
                ratio += units[idx];
            }
        }
        if (len > 0) {
            len = (int) Math.round(len / ratio);
        } else {
            len = 0;
        }
        for (int i = 0; i < units.length; i++) {
            if (units[i] > 1) {
                len += Math.round(units[i]);
            }
        }
        return len;
    }

    private Dimension getGridViewSize(boolean isPreferred)
    {
        int w = MINIMUM_VIEW_WIDTH, h = MINIMUM_VIEW_WIDTH;
        for (int i = 0, size = list.size(); i < size; i++) {
            ViewSpan span = (ViewSpan) list.get(i);
            View view = span.getView();
            Dimension viewSize = isPreferred ? view.getPreferredSize() : view.getMinimumSize();
            int col = span.getCol(), colCount = span.getColCount(), row = span.getRow(), rowCount = span.getRowCount();
            w = Math.max(w, antiNomalize(colWidths, col, colCount, viewSize.width));
            h = Math.max(h, antiNomalize(rowHeights, row, rowCount, viewSize.height));
        }
        return new Dimension(w, h);
    }

    private int getLength(int idx, int count, int nomalizedLength, double[] units)
    {
        double len = 0D;
        for (int i = 0; i < count; i++) {
            len += (units[idx + i] <= 1) ? nomalizedLength * units[idx + i] : units[idx + i];
        }
        return (int) len;
    }

    private void normalize(double[] units, double ratio, int exclude)
    {
        for (int i = 0; i < units.length; i++) {
            if (i != exclude && units[i] < 1) {
                units[i] *= ratio;
            }
        }
    }

    private void setSpanSize(ViewSpan span)
    {
        int col = span.getCol(), colCount = span.getColCount(), row = span.getRow(), rowCount = span.getRowCount();
        int x = getLength(0, col, normalizedSize.width, colWidths), width =
            getLength(col, colCount, normalizedSize.width, colWidths);
        int y = getLength(0, row, normalizedSize.height, rowHeights), height =
            getLength(row, rowCount, normalizedSize.height, rowHeights);
        span.setSpanRect(x, y, width, height);
    }

    public void dispose()
    {
        for (int i = 0, size = list.size(); i < size; i++) {
            ((ViewSpan) list.get(i)).getView().dispose();
        }
        list.clear();
        super.dispose();
    }

    /*
     * (non-Javadoc)
     * @see cn.sh.huang.chart.view.container.ViewContainer#getSubViewRect(cn.sh.huang.chart.view.AbstractView)
     */
    public Rectangle getGridRect(AbstractView view)
    {
        for (int i = 0, size = list.size(); i < size; i++) {
            ViewSpan span = (ViewSpan) list.get(i);
            if (view.equals(span.getView())) {
                return (Rectangle) span.getRect().clone();
            }
        }
        return null;
    }
}

class ViewSpan
{
    private final View view;
    private final Rectangle rect = new Rectangle();
    private final int col, colCount, row, rowCount;

    public ViewSpan(View view, int col, int colCount, int row, int rowCount)
    {
        this.view = view;
        this.col = col;
        this.colCount = colCount;
        this.row = row;
        this.rowCount = rowCount;
    }

    /**
     * @return the col
     */
    public final int getCol()
    {
        return col;
    }

    /**
     * @return the colCount
     */
    public final int getColCount()
    {
        return colCount;
    }

    /**
     * @return the row
     */
    public final int getRow()
    {
        return row;
    }

    /**
     * @return the rowCount
     */
    public final int getRowCount()
    {
        return rowCount;
    }

    /**
     * @return the view
     */
    public final View getView()
    {
        return view;
    }

    public void setSpanRect(int x, int y, int width, int height)
    {
        view.setViewSize(width, height);
        rect.setRect(x, y, width, height);
    }

    public Rectangle getRect()
    {
        return rect;
    }
}